﻿// Copyright 2012 Stuart Turner
// Licensed under the Apache License, Version 2.0 [http://www.apache.org/licenses/LICENSE-2.0]

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

using Complete.Data.Linq;

namespace Complete.Data.Linq.Expressions
{
	internal abstract class CompleteExpressionVisitor : Complete.Data.Support.ExpressionVisitor
	{
		protected override Expression Visit(Expression exp)
		{
			if (exp == null)
				return null;

			switch ((CompleteExpressionType)exp.NodeType)
			{
				case CompleteExpressionType.Select:
					return this.VisitSelect((SelectExpression)exp);
				case CompleteExpressionType.Column:
					return this.VisitColumn((ColumnExpression)exp);
				case CompleteExpressionType.From:
					return this.VisitFrom((FromExpression)exp);
				case CompleteExpressionType.Where:
					return this.VisitWhere((WhereExpression)exp);
				case CompleteExpressionType.Table:
					return this.VisitTable((TableExpression)exp);
				case CompleteExpressionType.Join:
					return this.VisitJoin((JoinExpression)exp);
				default:
					return base.Visit(exp);
			}
		}

		protected virtual Expression VisitTable(TableExpression table)
		{
			return table;
		}

		protected virtual Expression VisitJoin(JoinExpression join)
		{
			var left = this.Visit(join.Left) as SourceExpression;
			var right = this.Visit(join.Right) as SourceExpression;
			var comparison = join.Comparison != null ? this.Visit(join.Comparison) : null;

			if (left == join.Left &&
				right == join.Right &&
				comparison == join.Comparison)
				return join;

			return CompleteExpression.Join(left, right, join.JoinType, comparison);
		}

		protected virtual Expression VisitColumn(ColumnExpression column)
		{
			var exp = this.Visit(column.Declaration);
			if (exp != column.Declaration)
				return CompleteExpression.Column(exp, column.Name, column.Alias, column.Ordinal);
			return column;
		}

		protected virtual Expression VisitSelect(SelectExpression select)
		{
			var project = (LambdaExpression)this.Visit(select.Projection);
			var from = (FromExpression)this.Visit(select.From);
			var where = (WhereExpression)this.Visit(select.Where);
			var take = this.Visit(select.Take);
			var orderBy = this.VisitOrderBy(select.OrderBy);

			if (project == select.Projection &&
				from == select.From &&
				where == select.Where &&
				take == select.Take &&
				orderBy == select.OrderBy)
				return select;
			return CompleteExpression.Select(select.Alias, project, from, orderBy, where, select.IsProjection);
		}

		protected virtual Expression VisitFrom(FromExpression from)
		{
			var source = (SourceExpression)this.Visit(from.From);
			if (source == from.From)
				return from;
			return CompleteExpression.From(source);
		}

		protected virtual Expression VisitWhere(WhereExpression where)
		{
			var predicate = this.Visit(where.Predicate);
			if (predicate == where.Predicate)
				return where;
			return CompleteExpression.Where(predicate);
		}

		protected virtual OrderByClause VisitOrderBy(OrderByClause orderBy)
		{
			if (orderBy == null)
				return null;

			var secondary = this.VisitOrderBy(orderBy.SecondarySortingClause);
			var flag = secondary != orderBy.SecondarySortingClause;

			var newOrderBy = new OrderByClause(secondary);
			foreach (var col in orderBy.SortColumns)
			{
				var newCol = this.Visit(col.Item1);
				newOrderBy.AppendColumn(Tuple.Create(newCol, col.Item2));

				if (newCol != col.Item1)
					flag = true;
			}

			if (flag)
				return newOrderBy;
			return orderBy;
		}
	}
}
